﻿using RockBus.DataContracts;
using RockBus.Logging.Interfaces;
using RockBus.ServiceModel;
using RockBus.Transport.Interfaces;
using System;
using System.Configuration;
using System.Reflection;
using System.ServiceModel;
using System.ServiceModel.Configuration;
using System.ServiceModel.Routing;

namespace RockBus.Transport.Wcf
{
    public class TransportFactory : ITransportFactory
    {
        private static readonly BindingFactory BindingFactory = new BindingFactory();

        private const SecurityMode DefaultSecurityMode = SecurityMode.None;

        public TransportFactory(ILogger logger)
        {
            this.Logger = logger;
        }

        private ILogger Logger { get; set; }

        public string TransportName { get { return TransportTypeEnum.WcfTransport.ToString(); } }

        public ITransport CreateTransport(EndpointInfo key)
        {
            this.Logger.LogDebug(MethodBase.GetCurrentMethod(), "Creating transport for {0}", key.Address);

            switch (key.ExchangeType)
            {
                case ExchangeTypeEnum.OneWay:
                case ExchangeTypeEnum.OneWayRequestResponse:
                    return CreateOneWayTransport(key);

                case ExchangeTypeEnum.TwoWayAck:
                case ExchangeTypeEnum.TwoWayAckRequestResponse:
                case ExchangeTypeEnum.TwoWayRequestResponse:
                    return CreateTwoWayTransport(key);

                default:
                    return null;
            }
        }

        private ITransport CreateOneWayTransport(EndpointInfo key)
        {
            ClientBaseArgs args = this.CreateClientBaseArgs(key);
            ClientBaseEx<IOneWay> client = new OneWayClient(args);
            ITransport transport = new TransportOneWay(this.Logger, client as IOneWay, client.MessageVersion);
            return transport;
        }

        private ITransport CreateTwoWayTransport(EndpointInfo key)
        {
            ClientBaseArgs args = this.CreateClientBaseArgs(key);
            ClientBaseEx<ITwoWay> client = new TwoWayClient(args);
            ITransport transport = new TransportTwoWay(this.Logger, client as ITwoWay, client.MessageVersion);
            return transport;
        }

        private ClientBaseArgs CreateClientBaseArgs(EndpointInfo endpointInfo)
        {
            ClientBaseArgs args = null;

            var spb = new SoapProcessingBehavior();

            // Check in config if config name exists, if yes, construct args accordingly
            ChannelEndpointElementCollection endpointElements = new ChannelEndpointElementCollection();
            var section = ConfigurationManager.GetSection("system.serviceModel/client") as System.ServiceModel.Configuration.ClientSection;
            if (section != null)
            {
                endpointElements = section.Endpoints;
            }

            foreach (ChannelEndpointElement endpointElement in endpointElements)
            {
                if ((StringComparer.InvariantCultureIgnoreCase.Compare(
                    endpointElement.Name, endpointInfo.EndpointConfigurationName) == 0) ||
                    (endpointInfo.EndpointName == EndpointNameEnum.Custom))
                {
                    args = new ClientBaseArgs(endpointInfo.EndpointConfigurationName, endpointInfo.Address);
                    args.AppendEndpointBehavior(spb);
                    return args;
                }
            }

            // Otherwise, build Binding based on default binding.
            var binding = BindingFactory.CreateBinding(endpointInfo.EndpointName);

            args = new ClientBaseArgs(binding, endpointInfo.Address);
            args.AppendEndpointBehavior(spb);
            return args;
        }
    }
}